<template>
    <div class="websocket-tester">
        <back></back>
        <div class="header">
            <h1>WebSocket在线</h1>
        </div>

        <div class="main-content">
            <div class="connection-panel">
                <div class="url-input">
                    <div class="input-group">
                        <label>WebSocket URL:</label>
                        <div class="url-input-wrapper">
                            <input
                                type="text"
                                v-model="wsUrl"
                                placeholder="输入WebSocket URL (例如: ws://localhost:8080)"
                                @keyup.enter="toggleConnection"
                            >
                            <div class="url-actions">
                                <button class="url-action-btn" @click="saveUrl" title="保存URL">
                                    <i class="fas fa-save"></i>
                                </button>
                                <button class="url-action-btn" @click="loadSavedUrl" title="加载已保存URL">
                                    <i class="fas fa-history"></i>
                                </button>
                            </div>
                        </div>
                    </div>
                    <div class="connect-btn-wrapper">
                        <button
                            @click="toggleConnection"
                            :class="['connect-btn', {'connected': isConnected}]"
                        >
                            <i :class="['fas', isConnected ? 'fa-plug' : 'fa-plug']"></i>
                            {{ isConnected ? '断开连接' : '建立连接' }}
                        </button>
                        <button class="reconnect-btn" @click="reconnect" v-if="!isConnected" title="重新连接">
                            <i class="fas fa-sync-alt"></i>
                        </button>
                    </div>
                </div>

                <div class="connection-info">
                    <div class="status-group">
                        <div class="status-indicator" :class="connectionStatusClass"></div>
                        <span class="status-text">状态: {{ connectionStatus }}</span>
                    </div>
                    <div class="connection-details" v-if="isConnected">
                        <span class="connection-time">
                            <i class="far fa-clock"></i>
                            已连接时长: {{ connectionDuration }}
                        </span>
                        <span class="ping-time" v-if="pingLatency">
                            <i class="fas fa-tachometer-alt"></i>
                            延迟: {{ pingLatency }}ms
                        </span>
                    </div>
                    <div class="connection-actions">
                        <button class="action-btn" @click="pingServer" :disabled="!isConnected" title="测试连接">
                            <i class="fas fa-heartbeat"></i>
                        </button>
                        <button class="action-btn" @click="toggleAutoReconnect" :class="{'active': autoReconnect}" title="自动重连">
                            <i class="fas fa-sync"></i>
                        </button>
                    </div>
                </div>

                <div v-if="connectionStatus === '连接失败'" class="error-message">
                    <i class="fas fa-exclamation-triangle"></i>
                    连接失败，请检查URL或网络设置。
                    <button class="retry-btn" @click="connectWebSocket">
                        重试
                    </button>
                </div>
            </div>

            <div class="message-panel">
                <div class="panel-header">
                    <div class="panel-title">
                        <h2>消息记录</h2>
                        <span class="message-count" v-if="messages.length">
                            ({{ messages.length }})
                        </span>
                    </div>
                    <div class="header-controls">
                        <button class="filter-btn" @click="toggleMessageFilter" title="过滤消息">
                            <i class="fas fa-filter"></i>
                        </button>
                        <button class="search-btn" @click="toggleSearch" title="搜索消息">
                            <i class="fas fa-search"></i>
                        </button>
                        <button class="clear-btn" @click="clearMessages" :disabled="!messages.length">
                            <i class="fas fa-trash"></i> 清空记录
                        </button>
                        <button class="export-btn" @click="exportMessages" :disabled="!messages.length">
                            <i class="fas fa-download"></i> 导出记录
                        </button>
                    </div>
                </div>

                <div class="message-filters" v-if="showFilters">
                    <select v-model="messageTypeFilter">
                        <option value="all">全部消息</option>
                        <option value="sent">已发送</option>
                        <option value="received">已接收</option>
                    </select>
                    <input
                        type="text"
                        v-model="searchText"
                        placeholder="搜索消息内容..."
                        class="search-input"
                    >
                </div>

                <div class="message-history" ref="messageContainer">
                    <template v-if="filteredMessages.length">
                        <div v-for="(msg, index) in filteredMessages"
                             :key="index"
                             :class="['message', msg.type]">
                            <div class="message-header">
                                <span class="time">
                                    <i class="far fa-clock"></i>
                                    {{ msg.time }}
                                </span>
                                <span class="type" :class="msg.type">
                                    <i :class="['fas', msg.type === 'sent' ? 'fa-paper-plane' : 'fa-download']"></i>
                                    {{ msg.type === 'sent' ? '发送' : '接收' }}
                                </span>
                            </div>
                            <div class="message-body">
                                <pre class="message-content" v-html="formatMessage(msg.content)"></pre>
                                <div class="message-actions">
                                    <button class="action-btn" @click="copyMessage(msg.content)" title="复制">
                                        <i class="fas fa-copy"></i>
                                    </button>
                                    <button class="action-btn" @click="resendMessage(msg)" v-if="msg.type === 'sent'" title="重发">
                                        <i class="fas fa-redo"></i>
                                    </button>
                                    <button class="action-btn" @click="saveAsTemplate(msg.content)" title="保存为模板">
                                        <i class="fas fa-save"></i>
                                    </button>
                                </div>
                            </div>
                        </div>
                    </template>
                    <div v-else class="empty-state">
                        <i class="fas fa-inbox"></i>
                        暂无消息记录
                    </div>
                </div>

                <div class="message-composer">
                    <div class="composer-header">
                        <div class="format-selector">
                            <select v-model="messageFormat">
                                <option value="text">纯文本</option>
                                <option value="json">JSON</option>
                                <option value="xml">XML</option>
                            </select>
                        </div>
                        <div class="message-templates" v-if="messageFormat !== 'text'">
                            <button @click="loadTemplate('default')" class="template-btn">
                                <i class="fas fa-file-code"></i>
                                默认模板
                            </button>
                            <button @click="loadTemplate('custom')" class="template-btn">
                                <i class="fas fa-file-alt"></i>
                                自定义模板
                            </button>
                            <button @click="manageTemplates" class="template-btn">
                                <i class="fas fa-cog"></i>
                                管理模板
                            </button>
                        </div>
                    </div>

                    <div class="composer-body">
                        <textarea
                            v-model="messageInput"
                            :placeholder="getPlaceholder"
                            @keyup.ctrl.enter="sendMessage"
                            :class="{'json-input': messageFormat === 'json'}"
                        ></textarea>

                        <div class="composer-controls">
                            <div class="composer-options">
                                <label class="auto-format">
                                    <input type="checkbox" v-model="autoFormat">
                                    自动格式化
                                </label>
                                <span class="hint">提示: Ctrl + Enter 快捷发送</span>
                            </div>
                            <div class="send-controls">
                                <button
                                    class="validate-btn"
                                    @click="validateMessage"
                                    v-if="messageFormat !== 'text'"
                                    title="验证格式"
                                >
                                    <i class="fas fa-check-circle"></i>
                                </button>
                                <button
                                    class="send-btn"
                                    @click="sendMessage"
                                    :disabled="!isConnected || !messageInput.trim()"
                                >
                                    <i class="fas fa-paper-plane"></i> 发送
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- Toast消息组件 -->
        <div v-if="toast.show" class="toast" :class="toast.type">
            <i :class="['fas', getToastIcon]"></i>
            {{ toast.message }}
        </div>
    </div>
</template>


<script>
import Back from "@/components/back.vue";

export default {
    components: {Back},
    data() {
        return {
            wsUrl: '',
            isConnected: false,
            ws: null,
            messages: [],
            messageInput: '',
            messageFormat: 'text',
            connectionStatus: '未连接',
            connectionStatusClass: 'status-disconnected',
            connectionDuration: '',
            toast: { show: false, message: '', type: '' },
            autoReconnect: false,
            reconnectAttempts: 0,
            maxReconnectAttempts: 5,
            pingLatency: null,
            showFilters: false,
            messageTypeFilter: 'all',
            searchText: '',
            autoFormat: true,
            savedUrls: [],
            customTemplates: [],
            savedMessages: [],
            connectionTimer: null
        };
    },
    computed: {
        getPlaceholder() {
            return this.messageFormat === 'json' ? '请输入 JSON 格式的消息' : '请输入消息';
        },
        filteredMessages() {
            let filtered = this.messages;
            if (this.messageTypeFilter !== 'all') {
                filtered = filtered.filter(msg => msg.type === this.messageTypeFilter);
            }
            if (this.searchText) {
                const searchLower = this.searchText.toLowerCase();
                filtered = filtered.filter(msg =>
                    msg.content.toLowerCase().includes(searchLower)
                );
            }
            return filtered;
        },
        getToastIcon() {
            const icons = {
                success: 'fa-check-circle',
                error: 'fa-exclamation-circle',
                info: 'fa-info-circle',
                warning: 'fa-exclamation-triangle'
            };
            return icons[this.toast.type] || 'fa-info-circle';
        }
    },
    methods: {
        connectWebSocket() {
            try {
                this.ws = new WebSocket(this.wsUrl);
                this.connectionStatus = '正在连接...';
                this.connectionStatusClass = 'status-connecting';

                this.ws.onopen = () => {
                    this.isConnected = true;
                    this.connectionStatus = '已连接';
                    this.connectionStatusClass = 'status-connected';
                    this.reconnectAttempts = 0;
                    this.startConnectionTimer();
                    this.showToast('连接成功', 'success');
                };

                this.ws.onclose = () => {
                    this.isConnected = false;
                    this.connectionStatus = '已断开';
                    this.connectionStatusClass = 'status-disconnected';
                    this.stopConnectionTimer();

                    if (this.autoReconnect) {
                        this.reconnect();
                    }
                };

                this.ws.onerror = () => {
                    this.connectionStatus = '连接失败';
                    this.connectionStatusClass = 'status-error';
                    this.showToast('连接失败', 'error');
                };

                this.ws.onmessage = (event) => {
                    this.handleMessage(event.data, 'received');
                };

            } catch (error) {
                this.showToast('无效的WebSocket URL', 'error');
            }
        },

        disconnectWebSocket() {
            if (this.ws) {
                this.ws.close();
            }
        },

        toggleConnection() {
            if (this.isConnected) {
                this.disconnectWebSocket();
            } else {
                this.connectWebSocket();
            }
        },

        sendMessage() {
            if (!this.isConnected || !this.messageInput.trim()) {
                return;
            }

            try {
                let message = this.messageInput;
                if (this.messageFormat === 'json' && this.autoFormat) {
                    message = JSON.stringify(JSON.parse(message), null, 2);
                }

                this.ws.send(message);
                this.handleMessage(message, 'sent');
                this.messageInput = '';
                this.showToast('消息已发送', 'success');
            } catch (error) {
                this.showToast('发送失败: ' + error.message, 'error');
            }
        },

        handleMessage(content, type) {
            const time = new Date().toLocaleTimeString();
            this.messages.push({ content, type, time });
            this.$nextTick(() => {
                const container = this.$refs.messageContainer;
                container.scrollTop = container.scrollHeight;
            });
        },

        formatMessage(message) {
            try {
                if (this.messageFormat === 'json') {
                    return JSON.stringify(JSON.parse(message), null, 2);
                }
                return message;
            } catch {
                return message;
            }
        },

        clearMessages() {
            this.messages = [];
            this.showToast('消息记录已清空', 'info');
        },

        exportMessages() {
            const data = JSON.stringify(this.messages, null, 2);
            const blob = new Blob([data], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `websocket-messages-${new Date().toISOString()}.json`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            this.showToast('消息记录已导出', 'success');
        },

        copyMessage(content) {
            navigator.clipboard.writeText(content)
                .then(() => this.showToast('已复制到剪贴板', 'success'))
                .catch(() => this.showToast('复制失败', 'error'));
        },

        showToast(message, type = 'info') {
            this.toast = { show: true, message, type };
            setTimeout(() => {
                this.toast.show = false;
            }, 3000);
        },

        startConnectionTimer() {
            let startTime = Date.now();
            this.connectionTimer = setInterval(() => {
                const duration = Date.now() - startTime;
                const seconds = Math.floor(duration / 1000);
                const minutes = Math.floor(seconds / 60);
                const hours = Math.floor(minutes / 60);

                this.connectionDuration = `${hours}:${String(minutes % 60).padStart(2, '0')}:${String(seconds % 60).padStart(2, '0')}`;
            }, 1000);
        },

        stopConnectionTimer() {
            if (this.connectionTimer) {
                clearInterval(this.connectionTimer);
                this.connectionTimer = null;
                this.connectionDuration = '';
            }
        },

        pingServer() {
            const startTime = Date.now();
            this.ws.send('ping');
            this.ws.onmessage = (event) => {
                if (event.data === 'pong') {
                    this.pingLatency = Date.now() - startTime;
                }
            };
        },

        toggleAutoReconnect() {
            this.autoReconnect = !this.autoReconnect;
            this.showToast(
                this.autoReconnect ? '已开启自动重连' : '已关闭自动重连',
                'info'
            );
        },

        reconnect() {
            if (this.reconnectAttempts < this.maxReconnectAttempts) {
                this.reconnectAttempts++;
                setTimeout(() => {
                    this.connectWebSocket();
                }, 1000 * this.reconnectAttempts); // 递增重连延迟
            } else {
                this.showToast('重连次数超过限制', 'error');
                this.autoReconnect = false;
            }
        },

        saveUrl() {
            if (this.wsUrl && !this.savedUrls.includes(this.wsUrl)) {
                this.savedUrls.push(this.wsUrl);
                localStorage.setItem('savedUrls', JSON.stringify(this.savedUrls));
                this.showToast('URL已保存', 'success');
            }
        },

        loadSavedUrl() {
            if (this.savedUrls.length > 0) {
                // 显示一个简单的下拉菜单或弹窗来选择URL
                const url = this.savedUrls[this.savedUrls.length - 1]; // 这里简化为加载最新保存的URL
                this.wsUrl = url;
                this.showToast('已加载保存的URL', 'success');
            } else {
                this.showToast('没有保存的URL', 'info');
            }
        },

        validateMessage() {
            try {
                if (this.messageFormat === 'json') {
                    JSON.parse(this.messageInput);
                    this.showToast('JSON格式有效', 'success');
                } else if (this.messageFormat === 'xml') {
                    // 添加XML验证逻辑
                    this.showToast('XML格式验证功能开发中', 'info');
                }
            } catch (error) {
                this.showToast(`${this.messageFormat.toUpperCase()}格式无效: ${error.message}`, 'error');
            }
        },

        saveAsTemplate(content) {
            if (!this.customTemplates.includes(content)) {
                this.customTemplates.push(content);
                localStorage.setItem('customTemplates', JSON.stringify(this.customTemplates));
                this.showToast('已保存为模板', 'success');
            } else {
                this.showToast('模板已存在', 'info');
            }
        },

        loadTemplate(type) {
            if (type === 'default') {
                const defaultTemplates = {
                    json: '{\n  "type": "message",\n  "content": "Hello World"\n}',
                    xml: '<?xml version="1.0"?>\n<message>\n  <content>Hello World</content>\n</message>'
                };
                this.messageInput = defaultTemplates[this.messageFormat] || '';
            } else if (type === 'custom' && this.customTemplates.length > 0) {
                // 这里简化为加载最新的模板
                this.messageInput = this.customTemplates[this.customTemplates.length - 1];
            }
        },

        manageTemplates() {
            // 显示模板管理界面的逻辑
            this.showToast('模板管理功能开发中', 'info');
        },

        resendMessage(msg) {
            this.messageInput = msg.content;
            this.sendMessage();
        },

        toggleMessageFilter() {
            this.showFilters = !this.showFilters;
        },

        toggleSearch() {
            this.showFilters = !this.showFilters;
            if (this.showFilters) {
                this.$nextTick(() => {
                    const searchInput = document.querySelector('.search-input');
                    if (searchInput) {
                        searchInput.focus();
                    }
                });
            }
        }
    },
    created() {
        // 从localStorage加载保存的数据
        const savedUrls = localStorage.getItem('savedUrls');
        if (savedUrls) {
            this.savedUrls = JSON.parse(savedUrls);
        }

        const customTemplates = localStorage.getItem('customTemplates');
        if (customTemplates) {
            this.customTemplates = JSON.parse(customTemplates);
        }
    },
    beforeDestroy() {
        this.disconnectWebSocket();
        this.stopConnectionTimer();
    }
};
</script>


<style scoped>
.websocket-tester {
    min-height: 100vh;
    background: #f8f9fa;
    padding: 20px;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}

.header {
    margin-bottom: 20px;
}

.header h1 {
    color: #2c3e50;
    font-size: 24px;
    font-weight: 600;
    text-align: center;
    margin: 0;
    padding: 20px 0;
}

.main-content {
    display: flex;
    flex-direction: column;
    gap: 20px;
    max-width: 1200px;
    margin: 0 auto;
    padding: 20px;
    background-color: white;
    border-radius: 12px;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
}

.connection-panel {
    background: white;
    padding: 24px;
    border-radius: 12px;
    box-shadow: 0 2px 12px rgba(0,0,0,0.05);
}

.url-input {
    display: flex;
    gap: 20px;
    align-items: flex-start;
}

.input-group {
    flex: 1;
}

.input-group label {
    display: block;
    margin-bottom: 8px;
    color: #666;
    font-weight: 500;
}

.url-input-wrapper {
    position: relative;
    display: flex;
    align-items: center;
}

.url-input input {
    width: 100%;
    padding: 12px;
    border: 1px solid #ddd;
    border-radius: 8px;
    font-size: 14px;
    transition: all 0.3s ease;
    background: #fff;
}

.url-input input:focus {
    border-color: #409EFF;
    box-shadow: 0 0 0 2px rgba(64,158,255,0.1);
    outline: none;
}

.url-actions {
    position: absolute;
    right: 10px;
    display: flex;
    gap: 8px;
}

.url-action-btn {
    background: transparent;
    border: none;
    color: #909399;
    cursor: pointer;
    padding: 4px;
    transition: color 0.3s;
}

.url-action-btn:hover {
    color: #409EFF;
}

.connect-btn-wrapper {
    display: flex;
    gap: 8px;
    align-items: flex-start;
    margin-top: 24px;
}

.connect-btn {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 12px 24px;
    border: none;
    border-radius: 8px;
    background: #409EFF;
    color: white;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.3s;
    min-width: 140px;
    justify-content: center;
}

.connect-btn:hover {
    background: #66b1ff;
    transform: translateY(-1px);
}

.connect-btn.connected {
    background: #F56C6C;
}

.connect-btn.connected:hover {
    background: #f78989;
}

.reconnect-btn {
    padding: 12px;
    border: none;
    border-radius: 8px;
    background: #909399;
    color: white;
    cursor: pointer;
    transition: all 0.3s;
}

.reconnect-btn:hover {
    background: #a6a9ad;
}

.connection-info {
    margin-top: 20px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 12px;
    background: #f8f9fa;
    border-radius: 8px;
}

.status-group {
    display: flex;
    align-items: center;
    gap: 10px;
}

.status-indicator {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    transition: all 0.3s;
}

.status-connected {
    background: #67C23A;
    box-shadow: 0 0 0 4px rgba(103,194,58,0.1);
}

.status-disconnected {
    background: #F56C6C;
    box-shadow: 0 0 0 4px rgba(245,108,108,0.1);
}

.status-text {
    color: #606266;
    font-weight: 500;
}

.connection-details {
    display: flex;
    gap: 20px;
    color: #909399;
}

.connection-time, .ping-time {
    display: flex;
    align-items: center;
    gap: 6px;
}

.connection-actions {
    display: flex;
    gap: 8px;
}

.action-btn {
    padding: 8px;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: #909399;
    cursor: pointer;
    transition: all 0.3s;
}

.action-btn:hover {
    background: #f2f6fc;
    color: #409EFF;
}

.action-btn.active {
    background: #ecf5ff;
    color: #409EFF;
}

.error-message {
    margin-top: 15px;
    padding: 12px;
    background: #fef0f0;
    border-radius: 8px;
    color: #F56C6C;
    display: flex;
    align-items: center;
    gap: 10px;
}

.retry-btn {
    margin-left: auto;
    padding: 6px 12px;
    border: none;
    border-radius: 6px;
    background: #F56C6C;
    color: white;
    cursor: pointer;
    transition: all 0.3s;
}

.retry-btn:hover {
    background: #f78989;
}

.message-panel {
    background: white;
    border-radius: 12px;
    padding: 24px;
    box-shadow: 0 2px 12px rgba(0,0,0,0.05);
}

.panel-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
}

.panel-title {
    display: flex;
    align-items: center;
    gap: 10px;
}

.panel-title h2 {
    margin: 0;
    font-size: 18px;
    color: #2c3e50;
}

.message-count {
    color: #909399;
    font-size: 14px;
}

.header-controls {
    display: flex;
    gap: 10px;
}

.header-controls button {
    padding: 8px 16px;
    border: none;
    border-radius: 6px;
    font-size: 14px;
    cursor: pointer;
    transition: all 0.3s;
    display: flex;
    align-items: center;
    gap: 6px;
}

.filter-btn, .search-btn {
    background: #ecf5ff;
    color: #409EFF;
}

.filter-btn:hover, .search-btn:hover {
    background: #409EFF;
    color: white;
}

.clear-btn {
    background: #fef0f0;
    color: #F56C6C;
}

.export-btn {
    background: #f0f9eb;
    color: #67C23A;
}

.clear-btn:hover {
    background: #F56C6C;
    color: white;
}

.export-btn:hover {
    background: #67C23A;
    color: white;
}

.message-filters {
    display: flex;
    gap: 15px;
    margin-bottom: 15px;
    padding: 15px;
    background: #f8f9fa;
    border-radius: 8px;
}

.message-filters select,
.message-filters input {
    padding: 8px 12px;
    border: 1px solid #ddd;
    border-radius: 6px;
    font-size: 14px;
}

.message-filters input {
    flex: 1;
}

.message-history {
    max-height: 400px;
    overflow-y: auto;
    margin-bottom: 20px;
    padding: 10px;
    border: 1px solid #eee;
    border-radius: 8px;
}

.message {
    margin-bottom: 15px;
    animation: slideIn 0.3s ease;
}

.message-header {
    display: flex;
    justify-content: space-between;
    color: #909399;
    margin-bottom: 8px;
}

.message-body {
    background: #f8f9fa;
    padding: 15px;
    border-radius: 8px;
    position: relative;
}

.message.sent .message-body {
    background: #ecf5ff;
}

.message.received .message-body {
    background: #f0f9eb;
}

.message-content {
    margin: 0;
    white-space: pre-wrap;
    font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
    font-size: 14px;
}

.message-actions {
    position: absolute;
    top: 10px;
    right: 10px;
    display: flex;
    gap: 8px;
    opacity: 0;
    transition: opacity 0.3s;
}

.message-body:hover .message-actions {
    opacity: 1;
}

.empty-state {
    text-align: center;
    padding: 40px;
    color: #909399;
}

.empty-state i {
    font-size: 40px;
    margin-bottom: 10px;
    display: block;
}

.message-composer {
    background: #f8f9fa;
    padding: 20px;
    border-radius: 8px;
}

.composer-header {
    display: flex;
    justify-content: space-between;
    margin-bottom: 15px;
}

.format-selector select {
    padding: 8px 12px;
    border: 1px solid #ddd;
    border-radius: 6px;
    font-size: 14px;
}

.message-templates {
    display: flex;
    gap: 10px;
}

.template-btn {
    padding: 8px 16px;
    border: none;
    border-radius: 6px;
    background: #67C23A;
    color: white;
    cursor: pointer;
    transition: all 0.3s;
    display: flex;
    align-items: center;
    gap: 6px;
}

.template-btn:hover {
    background: #85ce61;
}

.composer-body textarea {
    width: 100%;
    padding: 15px;
    font-size: 14px;
    border: 1px solid #ddd;
    border-radius: 8px;
    resize: vertical;
    min-height: 120px;
    margin-bottom: 15px;
    transition: border-color 0.3s;
}

.composer-body textarea:focus {
    border-color: #409EFF;
    outline: none;
}

.json-input {
    font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
}

.composer-controls {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.composer-options {
    display: flex;
    align-items: center;
    gap: 20px;
}

.auto-format {
    display: flex;
    align-items: center;
    gap: 6px;
    color: #606266;
}

.hint {
    color: #909399;
    font-size: 12px;
}

.send-controls {
    display: flex;
    gap: 10px;
}

.validate-btn {
    padding: 10px;
    border: none;
    border-radius: 6px;
    background: #ecf5ff;
    color: #409EFF;
    cursor: pointer;
    transition: all 0.3s;
}

.validate-btn:hover {
    background: #409EFF;
    color: white;
}

.send-btn {
    padding: 10px 24px;
    border: none;
    border-radius: 6px;
    background: #409EFF;
    color: white;
    cursor: pointer;
    transition: all 0.3s;
    display: flex;
    align-items: center;
    gap: 8px;
}

.send-btn:hover {
    background: #66b1ff;
    transform: translateY(-1px);
}

.send-btn:disabled {
    background: #dcdfe6;
    cursor: not-allowed;
    transform: none;
}

.toast {
    position: fixed;
    bottom: 30px;
    left: 50%;
    transform: translateX(-50%);
    background-color: white;
    color: #2c3e50;
    padding: 15px 30px;
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    display: flex;
    align-items: center;
    gap: 10px;
    z-index: 1000;
    transition: all 0.3s ease;
}

.toast.success {
    background-color: #f0f9eb;
    color: #67C23A;
}

.toast.error {
    background-color: #fef0f0;
    color: #F56C6C;
}

.toast.info {
    background-color: #ecf5ff;
    color: #409EFF;
}

.toast.warning {
    background-color: #fdf6ec;
    color: #E6A23C;
}

@keyframes slideIn {
    from {
        transform: translateX(100%);
        opacity: 0;
    }
    to {
        transform: translateX(0);
        opacity: 1;
    }
}

/* 滚动条样式 */
::-webkit-scrollbar {
    width: 8px;
}

::-webkit-scrollbar-track {
    background: #f1f1f1;
    border-radius: 4px;
}

::-webkit-scrollbar-thumb {
    background: #c0c4cc;
    border-radius: 4px;
}

::-webkit-scrollbar-thumb:hover {
    background: #909399;
}
</style>
